The goal of this notebook is to compare label transfer results between:

  • Label transfer code with Azimuth currently in main at commit 6af112d. These results are referred to as "azimuth".
  • Label transfer code adapted from Azimuth. These results are referred to as "adapted_azimuth".

Setup

knitr::opts_chunk$set(message = FALSE, warning = FALSE)
options(future.globals.maxSize = 891289600000000)

suppressPackageStartupMessages({
  library(tidyverse)
  library(patchwork)
  library(Seurat)
})

repository_base <- rprojroot::find_root(rprojroot::is_git_root)
module_base <- file.path(repository_base, "analyses", "cell-type-wilms-tumor-06")
result_dir <- file.path(module_base, "results")


# functions to perform label transfer with azimuth-adapted approach
source(
  file.path(module_base, "notebook_template", "utils", "label-transfer-functions.R")
)

# Output files
full_results_file <- file.path(module_base, "scratch", "compare-label-transfer_fetal-full.rds")
kidney_results_file <- file.path(module_base, "scratch", "compare-label-transfer_fetal-kidney.rds")

Functions

# Make a heatmap of counts for label transfer strategies
plot_count_heatmap <- function(df, title, sample_id) {
  all_preds <- union(df$azimuth, df$adapted_azimuth)

  plotme <- data.frame(
    azimuth = all_preds,
    adapted_azimuth = all_preds
  ) |>
    expand(azimuth, adapted_azimuth) |>
    mutate(n = NA_integer_) |>
    anti_join(distinct(df)) |>
    bind_rows(
      df |> count(azimuth, adapted_azimuth)
    ) |>
    arrange(azimuth) |>
    mutate(
      color = case_when(
        is.na(n) ~ "white",
        n <= 20 ~ "grey90",
        n <= 50 ~ "lightblue",
        n <= 100 ~ "cornflowerblue",
        n <= 500 ~ "red",
        n <= 1000 ~ "yellow2",
        .default = "yellow"
      )
    )

  ggplot(plotme) +
    aes(x = azimuth, y = adapted_azimuth, fill = color, label = n) +
    geom_tile(alpha = 0.5) +
    geom_abline(color = "firebrick", alpha = 0.5) +
    geom_text(size = 3.5) +
    # scale_fill_viridis_c(name = "count", na.value = "grey90") +
    scale_fill_identity() +
    theme_bw() +
    theme(
      axis.text.y = element_text(size = 7),
      axis.text.x = element_text(angle = 30, size = 7, hjust = 1),
      legend.position = "bottom",
      legend.title = element_text(size = 9),
      legend.text = element_text(size = 8)
    ) +
    labs(
      title = glue::glue("{sample_id}: {str_to_title(title)}")
    )
}


# Wrapper function to compare results between approaches
# Makes two plots:
# - heatmap comparing counts for cell labels between approaches
# - density plot of annotation scores for labels that agree and disagree between approaches
compare <- function(df, compare_column, score_column, title) {
  spread_df <- df |>
    select({{ compare_column }}, barcode, version) |>
    pivot_wider(names_from = version, values_from = {{ compare_column }})


  heatmap <- plot_count_heatmap(spread_df, title, unique(df$sample_id))

  disagree_barcodes <- spread_df |>
    filter(azimuth != adapted_azimuth) |>
    pull(barcode)

  df2 <- df |>
    mutate(
      agree = ifelse(barcode %in% disagree_barcodes, "labels disagree", "labels agree"),
      agree = fct_relevel(agree, "labels disagree", "labels agree")
    )

  density_plot <- ggplot(df2) +
    aes(x = {{ score_column }}, fill = agree) +
    geom_density(alpha = 0.6) +
    theme_bw() +
    ggtitle(
      glue::glue("Disagree count: {length(disagree_barcodes)} out of {nrow(spread_df)}")
    ) +
    theme(legend.position = "bottom")

  print(heatmap + density_plot + plot_layout(widths = c(2, 1)))
}

Label transfer

This section both:

  • Reads in existing Azimuth label transfer results
  • Performs label transfer with Azimuth-adapted approach

If results are already available, we read in the files rather than regenerating results.

# sample ids to process
sample_ids <- c("SCPCS000179", "SCPCS000184", "SCPCS000194", "SCPCS000205", "SCPCS000208")

# read in seurat input objects, as needed
if ((!file.exists(full_results_file)) || (!file.exists(kidney_results_file))) {
  srat_objects <- sample_ids |>
    purrr::map(
      \(id) {
        srat <- readRDS(
          file.path(result_dir, id, glue::glue("01-Seurat_{id}.Rds"))
        )
        DefaultAssay(srat) <- "RNA"

        return(srat)
      }
    )
  names(srat_objects) <- sample_ids
}

Label transfer for fetal full

if (!file.exists(full_results_file)) {
  # read reference
  ref <- readRDS(file.path(
    module_base,
    "results",
    "references",
    "cao_formatted_ref.rds"
  ))
  full_reference <- ref$reference
  full_refdata <- ref$refdata
  full_dims <- ref$dims
  full_annotation_columns <- c(
    glue::glue("predicted.{ref$annotation_levels}"),
    glue::glue("predicted.{ref$annotation_levels}.score")
  )


  # Perform label transfer with new code
  assay <- "RNA"
  fetal_full <- srat_objects |>
    purrr::imap(
      \(srat, id) {
        
        set.seed(params$seed)

        query <- prepare_query(
          srat, 
          rownames(full_reference), 
          assay, 
          file.path(module_base, "scratch", "homologs.rds")
        )
        query <- transfer_labels(
          query,
          full_reference,
          full_dims,
          full_refdata, 
          query.assay = assay
        )

        # Read in results from existing Azimuth label transfer code
        srat_02a <- readRDS(
          file.path(result_dir, id, glue::glue("02a-fetal_full_label-transfer_{id}.Rds"))
        )

        # create final data frame with all annotations
        query@meta.data[, full_annotation_columns] |>
          tibble::rownames_to_column(var = "barcode") |>
          mutate(
            sample_id = id,
            version = "adapted_azimuth"
          ) |>
          # existing results
          bind_rows(
            data.frame(
              sample_id = id,
              barcode = colnames(srat_02a),
              version = "azimuth",
              predicted.annotation.l1 = srat_02a$fetal_full_predicted.annotation.l1,
              predicted.annotation.l1.score = srat_02a$fetal_full_predicted.annotation.l1.score,
              predicted.annotation.l2 = srat_02a$fetal_full_predicted.annotation.l2,
              predicted.annotation.l2.score = srat_02a$fetal_full_predicted.annotation.l2.score,
              predicted.organ = srat_02a$fetal_full_predicted.organ,
              predicted.organ.score = srat_02a$fetal_full_predicted.organ.score
            )
          )
      }
    )
  write_rds(fetal_full, full_results_file)
} else {
  fetal_full <- read_rds(full_results_file)
}

Label transfer for fetal kidney

if (!file.exists(kidney_results_file)) {
  # read reference
  ref <- readRDS(file.path(
    module_base,
    "results",
    "references",
    "stewart_formatted_ref.rds"
  ))

  # Pull out information from the reference object we need for label transfer
  kidney_reference <- ref$reference
  kidney_refdata <- ref$refdata
  kidney_dims <- ref$dims
  kidney_annotation_columns <- c(
    glue::glue("predicted.{ref$annotation_levels}"),
    glue::glue("predicted.{ref$annotation_levels}.score")
  )


  # Perform label transfer with new code
  assay <- "RNA"
  fetal_kidney <- srat_objects |>
    purrr::imap(
      \(srat, id) {
        set.seed(params$seed)

        query <- prepare_query(
          srat, 
          rownames(kidney_reference), 
          assay, 
          file.path(module_base, "scratch", "homologs.rds")
        )
        query <- transfer_labels(
          query,
          kidney_reference,
          kidney_dims,
          kidney_refdata,
          query.assay = assay
        )

        # Read in results from existing Azimuth label transfer code
        srat_02b <- readRDS(
          file.path(result_dir, id, glue::glue("02b-fetal_kidney_label-transfer_{id}.Rds"))
        )

        # create final data frame with all annotations
        query@meta.data[, kidney_annotation_columns] |>
          tibble::rownames_to_column(var = "barcode") |>
          mutate(
            sample_id = id,
            version = "adapted_azimuth"
          ) |>
          # existing results
          bind_rows(
            data.frame(
              sample_id = id,
              barcode = colnames(srat_02b),
              version = "azimuth",
              predicted.compartment = srat_02b$fetal_kidney_predicted.compartment,
              predicted.compartment.score = srat_02b$fetal_kidney_predicted.compartment.score,
              predicted.cell_type = srat_02b$fetal_kidney_predicted.cell_type,
              predicted.cell_type.score = srat_02b$fetal_kidney_predicted.cell_type.score
            )
          )
      }
    )

  write_rds(fetal_kidney, kidney_results_file)
} else {
  fetal_kidney <- read_rds(kidney_results_file)
}

Compare results

We expect: - The majority of annotations match between approaches, with heatmap counts primarily falling along the diagonal - Any annotations that disagree should have low scores

Fetal full reference

Note that results from the L2 reference are not plotted because they are not used in cell type annotation.

fetal_full |>
  purrr::walk(
    \(dat) {
      compare(dat, predicted.annotation.l1, predicted.annotation.l1.score, "l1")
      compare(dat, predicted.organ, predicted.organ.score, "organ")
    }
  )

Fetal kidney reference

fetal_kidney |>
  purrr::walk(
    \(dat) {
      compare(dat, predicted.compartment, predicted.compartment.score, "compartment")
      compare(dat, predicted.cell_type, predicted.cell_type.score, "cell_type")
    }
  )

Conclusions

The vast majority of the time, labels agree. Generally speaking, when labels do not agree, their annotation scores are much lower, which is as expected.

Additional notable differences are shown in tables below:

Fetal full reference:

  • The Azimuth-adapted approach occasionally calls kidney or kidney-related cells as intestine or intestine epithelial
  • Some other kidney-related differences are noted:
Sample Reference Count Azimuth Azimuth-adapted
SCPSC000179 L1 70 Metanephritic cells Intestinal epithelial cells
SCPSC000179 Organ 64 Kidney Intestine
SCPSC000179 Organ 20 Lung Kidney
SCPSC000194 L1 60 Stromal cells Mesangial cells
SCPSC000194 Organ 35 Kidney Intestine
SCPSC000194 Organ 36 Lung Kidney
SCPSC000205 Organ 56 Kidney Intestine
SCPSC000208 L1 101 Mesangial cells Metanephritic cells
SCPSC000208 L1 75 Intestinal epithelial cells Metanephritic cells
SCPSC000208 Organ 149 Kidney Intestine

Fetal kidney reference:

  • Most of the cell type differences are not in the table below because they are not necessarily biologically meaningful for our purposes:
    • kidney cell vs podocyte
    • kidney epithelial cell vs kidney cell
    • mesenchymal cell vs mesenchymal stem cell
Sample Reference Count Azimuth Azimuth-adapted
SCPSC000179 cell type 94 mesenchymal cell kidney epithelial cell
SCPSC000205 compartment 52 fetal nephron stroma

Session Info

sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: aarch64-apple-darwin20
Running under: macOS 15.1

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets 
[5] utils     methods   base     

other attached packages:
 [1] Seurat_5.1.0       SeuratObject_5.0.2
 [3] sp_2.1-4           patchwork_1.2.0   
 [5] lubridate_1.9.3    forcats_1.0.0     
 [7] stringr_1.5.1      dplyr_1.1.4       
 [9] purrr_1.0.2        readr_2.1.5       
[11] tidyr_1.3.1        tibble_3.2.1      
[13] ggplot2_3.5.1      tidyverse_2.0.0   

loaded via a namespace (and not attached):
  [1] deldir_2.0-4          
  [2] pbapply_1.7-2         
  [3] gridExtra_2.3         
  [4] rlang_1.1.4           
  [5] magrittr_2.0.3        
  [6] RcppAnnoy_0.0.22      
  [7] spatstat.geom_3.3-2   
  [8] matrixStats_1.3.0     
  [9] ggridges_0.5.6        
 [10] compiler_4.4.1        
 [11] png_0.1-8             
 [12] vctrs_0.6.5           
 [13] reshape2_1.4.4        
 [14] pkgconfig_2.0.3       
 [15] fastmap_1.2.0         
 [16] labeling_0.4.3        
 [17] utf8_1.2.4            
 [18] promises_1.3.0        
 [19] tzdb_0.4.0            
 [20] xfun_0.47             
 [21] jsonlite_1.8.8        
 [22] goftest_1.2-3         
 [23] later_1.3.2           
 [24] spatstat.utils_3.1-0  
 [25] irlba_2.3.5.1         
 [26] parallel_4.4.1        
 [27] cluster_2.1.6         
 [28] R6_2.5.1              
 [29] ica_1.0-3             
 [30] spatstat.data_3.1-2   
 [31] stringi_1.8.4         
 [32] RColorBrewer_1.1-3    
 [33] reticulate_1.38.0     
 [34] spatstat.univar_3.0-0 
 [35] parallelly_1.38.0     
 [36] lmtest_0.9-40         
 [37] scattermore_1.2       
 [38] Rcpp_1.0.13           
 [39] knitr_1.48            
 [40] tensor_1.5            
 [41] future.apply_1.11.2   
 [42] zoo_1.8-12            
 [43] sctransform_0.4.1     
 [44] httpuv_1.6.15         
 [45] Matrix_1.7-0          
 [46] splines_4.4.1         
 [47] igraph_2.0.3          
 [48] timechange_0.3.0      
 [49] tidyselect_1.2.1      
 [50] abind_1.4-5           
 [51] rstudioapi_0.16.0     
 [52] yaml_2.3.10           
 [53] spatstat.random_3.3-1 
 [54] spatstat.explore_3.3-2
 [55] codetools_0.2-20      
 [56] miniUI_0.1.1.1        
 [57] listenv_0.9.1         
 [58] lattice_0.22-6        
 [59] plyr_1.8.9            
 [60] shiny_1.9.1           
 [61] withr_3.0.1           
 [62] ROCR_1.0-11           
 [63] Rtsne_0.17            
 [64] future_1.34.0         
 [65] fastDummies_1.7.4     
 [66] survival_3.7-0        
 [67] polyclip_1.10-7       
 [68] fitdistrplus_1.2-1    
 [69] pillar_1.9.0          
 [70] BiocManager_1.30.25   
 [71] KernSmooth_2.23-24    
 [72] renv_1.0.7            
 [73] plotly_4.10.4         
 [74] generics_0.1.3        
 [75] rprojroot_2.0.4       
 [76] RcppHNSW_0.6.0        
 [77] hms_1.1.3             
 [78] munsell_0.5.1         
 [79] scales_1.3.0          
 [80] globals_0.16.3        
 [81] xtable_1.8-4          
 [82] glue_1.7.0            
 [83] lazyeval_0.2.2        
 [84] tools_4.4.1           
 [85] data.table_1.16.0     
 [86] RSpectra_0.16-2       
 [87] RANN_2.6.2            
 [88] leiden_0.4.3.1        
 [89] dotCall64_1.1-1       
 [90] cowplot_1.1.3         
 [91] grid_4.4.1            
 [92] colorspace_2.1-1      
 [93] nlme_3.1-166          
 [94] cli_3.6.3             
 [95] spatstat.sparse_3.1-0 
 [96] spam_2.10-0           
 [97] fansi_1.0.6           
 [98] viridisLite_0.4.2     
 [99] uwot_0.2.2            
[100] gtable_0.3.5          
[101] digest_0.6.37         
[102] progressr_0.14.0      
[103] ggrepel_0.9.5         
[104] farver_2.1.2          
[105] htmlwidgets_1.6.4     
[106] htmltools_0.5.8.1     
[107] lifecycle_1.0.4       
[108] httr_1.4.7            
[109] mime_0.12             
[110] MASS_7.3-61           
LS0tCnRpdGxlOiAiQ29tcGFyZSBsYWJlbCB0cmFuc2ZlciByZXN1bHRzIGJldHdlZW4gQXppbXV0aCBhbmQgQXppbXV0aC1hZGFwdGVkIHN0cmF0ZWd5IgphdXRob3I6IFN0ZXBoYW5pZSBTcGllbG1hbiwgRGF0YSBMYWIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwpwYXJhbXM6CiAgc2VlZDogMTIzNDUKLS0tCgoKVGhlIGdvYWwgb2YgdGhpcyBub3RlYm9vayBpcyB0byBjb21wYXJlIGxhYmVsIHRyYW5zZmVyIHJlc3VsdHMgYmV0d2VlbjoKCi0gTGFiZWwgdHJhbnNmZXIgY29kZSB3aXRoIEF6aW11dGggY3VycmVudGx5IGluIGBtYWluYCBhdCBjb21taXQgYDZhZjExMmRgLiBUaGVzZSByZXN1bHRzIGFyZSByZWZlcnJlZCB0byBhcyBgImF6aW11dGgiYC4KLSBMYWJlbCB0cmFuc2ZlciBjb2RlIGFkYXB0ZWQgZnJvbSBBemltdXRoLiBUaGVzZSByZXN1bHRzIGFyZSByZWZlcnJlZCB0byBhcyBgImFkYXB0ZWRfYXppbXV0aCJgLgoKCiMjIFNldHVwCgpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQpvcHRpb25zKGZ1dHVyZS5nbG9iYWxzLm1heFNpemUgPSA4OTEyODk2MDAwMDAwMDApCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkodGlkeXZlcnNlKQogIGxpYnJhcnkocGF0Y2h3b3JrKQogIGxpYnJhcnkoU2V1cmF0KQp9KQoKcmVwb3NpdG9yeV9iYXNlIDwtIHJwcm9qcm9vdDo6ZmluZF9yb290KHJwcm9qcm9vdDo6aXNfZ2l0X3Jvb3QpCm1vZHVsZV9iYXNlIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJjZWxsLXR5cGUtd2lsbXMtdHVtb3ItMDYiKQpyZXN1bHRfZGlyIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInJlc3VsdHMiKQoKCiMgZnVuY3Rpb25zIHRvIHBlcmZvcm0gbGFiZWwgdHJhbnNmZXIgd2l0aCBhemltdXRoLWFkYXB0ZWQgYXBwcm9hY2gKc291cmNlKAogIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgIm5vdGVib29rX3RlbXBsYXRlIiwgInV0aWxzIiwgImxhYmVsLXRyYW5zZmVyLWZ1bmN0aW9ucy5SIikKKQoKIyBPdXRwdXQgZmlsZXMKZnVsbF9yZXN1bHRzX2ZpbGUgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAic2NyYXRjaCIsICJjb21wYXJlLWxhYmVsLXRyYW5zZmVyX2ZldGFsLWZ1bGwucmRzIikKa2lkbmV5X3Jlc3VsdHNfZmlsZSA8LSBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJzY3JhdGNoIiwgImNvbXBhcmUtbGFiZWwtdHJhbnNmZXJfZmV0YWwta2lkbmV5LnJkcyIpCmBgYAoKIyMgRnVuY3Rpb25zCgpgYGB7ciBmdW5jdGlvbnN9CiMgTWFrZSBhIGhlYXRtYXAgb2YgY291bnRzIGZvciBsYWJlbCB0cmFuc2ZlciBzdHJhdGVnaWVzCnBsb3RfY291bnRfaGVhdG1hcCA8LSBmdW5jdGlvbihkZiwgdGl0bGUsIHNhbXBsZV9pZCkgewogIGFsbF9wcmVkcyA8LSB1bmlvbihkZiRhemltdXRoLCBkZiRhZGFwdGVkX2F6aW11dGgpCgogIHBsb3RtZSA8LSBkYXRhLmZyYW1lKAogICAgYXppbXV0aCA9IGFsbF9wcmVkcywKICAgIGFkYXB0ZWRfYXppbXV0aCA9IGFsbF9wcmVkcwogICkgfD4KICAgIGV4cGFuZChhemltdXRoLCBhZGFwdGVkX2F6aW11dGgpIHw+CiAgICBtdXRhdGUobiA9IE5BX2ludGVnZXJfKSB8PgogICAgYW50aV9qb2luKGRpc3RpbmN0KGRmKSkgfD4KICAgIGJpbmRfcm93cygKICAgICAgZGYgfD4gY291bnQoYXppbXV0aCwgYWRhcHRlZF9hemltdXRoKQogICAgKSB8PgogICAgYXJyYW5nZShhemltdXRoKSB8PgogICAgbXV0YXRlKAogICAgICBjb2xvciA9IGNhc2Vfd2hlbigKICAgICAgICBpcy5uYShuKSB+ICJ3aGl0ZSIsCiAgICAgICAgbiA8PSAyMCB+ICJncmV5OTAiLAogICAgICAgIG4gPD0gNTAgfiAibGlnaHRibHVlIiwKICAgICAgICBuIDw9IDEwMCB+ICJjb3JuZmxvd2VyYmx1ZSIsCiAgICAgICAgbiA8PSA1MDAgfiAicmVkIiwKICAgICAgICBuIDw9IDEwMDAgfiAieWVsbG93MiIsCiAgICAgICAgLmRlZmF1bHQgPSAieWVsbG93IgogICAgICApCiAgICApCgogIGdncGxvdChwbG90bWUpICsKICAgIGFlcyh4ID0gYXppbXV0aCwgeSA9IGFkYXB0ZWRfYXppbXV0aCwgZmlsbCA9IGNvbG9yLCBsYWJlbCA9IG4pICsKICAgIGdlb21fdGlsZShhbHBoYSA9IDAuNSkgKwogICAgZ2VvbV9hYmxpbmUoY29sb3IgPSAiZmlyZWJyaWNrIiwgYWxwaGEgPSAwLjUpICsKICAgIGdlb21fdGV4dChzaXplID0gMy41KSArCiAgICAjIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG5hbWUgPSAiY291bnQiLCBuYS52YWx1ZSA9ICJncmV5OTAiKSArCiAgICBzY2FsZV9maWxsX2lkZW50aXR5KCkgKwogICAgdGhlbWVfYncoKSArCiAgICB0aGVtZSgKICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDMwLCBzaXplID0gNywgaGp1c3QgPSAxKSwKICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSksCiAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KQogICAgKSArCiAgICBsYWJzKAogICAgICB0aXRsZSA9IGdsdWU6OmdsdWUoIntzYW1wbGVfaWR9OiB7c3RyX3RvX3RpdGxlKHRpdGxlKX0iKQogICAgKQp9CgoKIyBXcmFwcGVyIGZ1bmN0aW9uIHRvIGNvbXBhcmUgcmVzdWx0cyBiZXR3ZWVuIGFwcHJvYWNoZXMKIyBNYWtlcyB0d28gcGxvdHM6CiMgLSBoZWF0bWFwIGNvbXBhcmluZyBjb3VudHMgZm9yIGNlbGwgbGFiZWxzIGJldHdlZW4gYXBwcm9hY2hlcwojIC0gZGVuc2l0eSBwbG90IG9mIGFubm90YXRpb24gc2NvcmVzIGZvciBsYWJlbHMgdGhhdCBhZ3JlZSBhbmQgZGlzYWdyZWUgYmV0d2VlbiBhcHByb2FjaGVzCmNvbXBhcmUgPC0gZnVuY3Rpb24oZGYsIGNvbXBhcmVfY29sdW1uLCBzY29yZV9jb2x1bW4sIHRpdGxlKSB7CiAgc3ByZWFkX2RmIDwtIGRmIHw+CiAgICBzZWxlY3Qoe3sgY29tcGFyZV9jb2x1bW4gfX0sIGJhcmNvZGUsIHZlcnNpb24pIHw+CiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdmVyc2lvbiwgdmFsdWVzX2Zyb20gPSB7eyBjb21wYXJlX2NvbHVtbiB9fSkKCgogIGhlYXRtYXAgPC0gcGxvdF9jb3VudF9oZWF0bWFwKHNwcmVhZF9kZiwgdGl0bGUsIHVuaXF1ZShkZiRzYW1wbGVfaWQpKQoKICBkaXNhZ3JlZV9iYXJjb2RlcyA8LSBzcHJlYWRfZGYgfD4KICAgIGZpbHRlcihhemltdXRoICE9IGFkYXB0ZWRfYXppbXV0aCkgfD4KICAgIHB1bGwoYmFyY29kZSkKCiAgZGYyIDwtIGRmIHw+CiAgICBtdXRhdGUoCiAgICAgIGFncmVlID0gaWZlbHNlKGJhcmNvZGUgJWluJSBkaXNhZ3JlZV9iYXJjb2RlcywgImxhYmVscyBkaXNhZ3JlZSIsICJsYWJlbHMgYWdyZWUiKSwKICAgICAgYWdyZWUgPSBmY3RfcmVsZXZlbChhZ3JlZSwgImxhYmVscyBkaXNhZ3JlZSIsICJsYWJlbHMgYWdyZWUiKQogICAgKQoKICBkZW5zaXR5X3Bsb3QgPC0gZ2dwbG90KGRmMikgKwogICAgYWVzKHggPSB7eyBzY29yZV9jb2x1bW4gfX0sIGZpbGwgPSBhZ3JlZSkgKwogICAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC42KSArCiAgICB0aGVtZV9idygpICsKICAgIGdndGl0bGUoCiAgICAgIGdsdWU6OmdsdWUoIkRpc2FncmVlIGNvdW50OiB7bGVuZ3RoKGRpc2FncmVlX2JhcmNvZGVzKX0gb3V0IG9mIHtucm93KHNwcmVhZF9kZil9IikKICAgICkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgogIHByaW50KGhlYXRtYXAgKyBkZW5zaXR5X3Bsb3QgKyBwbG90X2xheW91dCh3aWR0aHMgPSBjKDIsIDEpKSkKfQpgYGAKCgojIyBMYWJlbCB0cmFuc2ZlcgoKVGhpcyBzZWN0aW9uIGJvdGg6CgotIFJlYWRzIGluIGV4aXN0aW5nIEF6aW11dGggbGFiZWwgdHJhbnNmZXIgcmVzdWx0cwotIFBlcmZvcm1zIGxhYmVsIHRyYW5zZmVyIHdpdGggQXppbXV0aC1hZGFwdGVkIGFwcHJvYWNoCgpJZiByZXN1bHRzIGFyZSBhbHJlYWR5IGF2YWlsYWJsZSwgd2UgcmVhZCBpbiB0aGUgZmlsZXMgcmF0aGVyIHRoYW4gcmVnZW5lcmF0aW5nIHJlc3VsdHMuCgpgYGB7cn0KIyBzYW1wbGUgaWRzIHRvIHByb2Nlc3MKc2FtcGxlX2lkcyA8LSBjKCJTQ1BDUzAwMDE3OSIsICJTQ1BDUzAwMDE4NCIsICJTQ1BDUzAwMDE5NCIsICJTQ1BDUzAwMDIwNSIsICJTQ1BDUzAwMDIwOCIpCgojIHJlYWQgaW4gc2V1cmF0IGlucHV0IG9iamVjdHMsIGFzIG5lZWRlZAppZiAoKCFmaWxlLmV4aXN0cyhmdWxsX3Jlc3VsdHNfZmlsZSkpIHx8ICghZmlsZS5leGlzdHMoa2lkbmV5X3Jlc3VsdHNfZmlsZSkpKSB7CiAgc3JhdF9vYmplY3RzIDwtIHNhbXBsZV9pZHMgfD4KICAgIHB1cnJyOjptYXAoCiAgICAgIFwoaWQpIHsKICAgICAgICBzcmF0IDwtIHJlYWRSRFMoCiAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0X2RpciwgaWQsIGdsdWU6OmdsdWUoIjAxLVNldXJhdF97aWR9LlJkcyIpKQogICAgICAgICkKICAgICAgICBEZWZhdWx0QXNzYXkoc3JhdCkgPC0gIlJOQSIKCiAgICAgICAgcmV0dXJuKHNyYXQpCiAgICAgIH0KICAgICkKICBuYW1lcyhzcmF0X29iamVjdHMpIDwtIHNhbXBsZV9pZHMKfQpgYGAKCgojIyMgTGFiZWwgdHJhbnNmZXIgZm9yIGZldGFsIGZ1bGwKCmBgYHtyfQppZiAoIWZpbGUuZXhpc3RzKGZ1bGxfcmVzdWx0c19maWxlKSkgewogICMgcmVhZCByZWZlcmVuY2UKICByZWYgPC0gcmVhZFJEUyhmaWxlLnBhdGgoCiAgICBtb2R1bGVfYmFzZSwKICAgICJyZXN1bHRzIiwKICAgICJyZWZlcmVuY2VzIiwKICAgICJjYW9fZm9ybWF0dGVkX3JlZi5yZHMiCiAgKSkKICBmdWxsX3JlZmVyZW5jZSA8LSByZWYkcmVmZXJlbmNlCiAgZnVsbF9yZWZkYXRhIDwtIHJlZiRyZWZkYXRhCiAgZnVsbF9kaW1zIDwtIHJlZiRkaW1zCiAgZnVsbF9hbm5vdGF0aW9uX2NvbHVtbnMgPC0gYygKICAgIGdsdWU6OmdsdWUoInByZWRpY3RlZC57cmVmJGFubm90YXRpb25fbGV2ZWxzfSIpLAogICAgZ2x1ZTo6Z2x1ZSgicHJlZGljdGVkLntyZWYkYW5ub3RhdGlvbl9sZXZlbHN9LnNjb3JlIikKICApCgoKICAjIFBlcmZvcm0gbGFiZWwgdHJhbnNmZXIgd2l0aCBuZXcgY29kZQogIGFzc2F5IDwtICJSTkEiCiAgZmV0YWxfZnVsbCA8LSBzcmF0X29iamVjdHMgfD4KICAgIHB1cnJyOjppbWFwKAogICAgICBcKHNyYXQsIGlkKSB7CiAgICAgICAgCiAgICAgICAgc2V0LnNlZWQocGFyYW1zJHNlZWQpCgogICAgICAgIHF1ZXJ5IDwtIHByZXBhcmVfcXVlcnkoCiAgICAgICAgICBzcmF0LCAKICAgICAgICAgIHJvd25hbWVzKGZ1bGxfcmVmZXJlbmNlKSwgCiAgICAgICAgICBhc3NheSwgCiAgICAgICAgICBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJzY3JhdGNoIiwgImhvbW9sb2dzLnJkcyIpCiAgICAgICAgKQogICAgICAgIHF1ZXJ5IDwtIHRyYW5zZmVyX2xhYmVscygKICAgICAgICAgIHF1ZXJ5LAogICAgICAgICAgZnVsbF9yZWZlcmVuY2UsCiAgICAgICAgICBmdWxsX2RpbXMsCiAgICAgICAgICBmdWxsX3JlZmRhdGEsIAogICAgICAgICAgcXVlcnkuYXNzYXkgPSBhc3NheQogICAgICAgICkKCiAgICAgICAgIyBSZWFkIGluIHJlc3VsdHMgZnJvbSBleGlzdGluZyBBemltdXRoIGxhYmVsIHRyYW5zZmVyIGNvZGUKICAgICAgICBzcmF0XzAyYSA8LSByZWFkUkRTKAogICAgICAgICAgZmlsZS5wYXRoKHJlc3VsdF9kaXIsIGlkLCBnbHVlOjpnbHVlKCIwMmEtZmV0YWxfZnVsbF9sYWJlbC10cmFuc2Zlcl97aWR9LlJkcyIpKQogICAgICAgICkKCiAgICAgICAgIyBjcmVhdGUgZmluYWwgZGF0YSBmcmFtZSB3aXRoIGFsbCBhbm5vdGF0aW9ucwogICAgICAgIHF1ZXJ5QG1ldGEuZGF0YVssIGZ1bGxfYW5ub3RhdGlvbl9jb2x1bW5zXSB8PgogICAgICAgICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gImJhcmNvZGUiKSB8PgogICAgICAgICAgbXV0YXRlKAogICAgICAgICAgICBzYW1wbGVfaWQgPSBpZCwKICAgICAgICAgICAgdmVyc2lvbiA9ICJhZGFwdGVkX2F6aW11dGgiCiAgICAgICAgICApIHw+CiAgICAgICAgICAjIGV4aXN0aW5nIHJlc3VsdHMKICAgICAgICAgIGJpbmRfcm93cygKICAgICAgICAgICAgZGF0YS5mcmFtZSgKICAgICAgICAgICAgICBzYW1wbGVfaWQgPSBpZCwKICAgICAgICAgICAgICBiYXJjb2RlID0gY29sbmFtZXMoc3JhdF8wMmEpLAogICAgICAgICAgICAgIHZlcnNpb24gPSAiYXppbXV0aCIsCiAgICAgICAgICAgICAgcHJlZGljdGVkLmFubm90YXRpb24ubDEgPSBzcmF0XzAyYSRmZXRhbF9mdWxsX3ByZWRpY3RlZC5hbm5vdGF0aW9uLmwxLAogICAgICAgICAgICAgIHByZWRpY3RlZC5hbm5vdGF0aW9uLmwxLnNjb3JlID0gc3JhdF8wMmEkZmV0YWxfZnVsbF9wcmVkaWN0ZWQuYW5ub3RhdGlvbi5sMS5zY29yZSwKICAgICAgICAgICAgICBwcmVkaWN0ZWQuYW5ub3RhdGlvbi5sMiA9IHNyYXRfMDJhJGZldGFsX2Z1bGxfcHJlZGljdGVkLmFubm90YXRpb24ubDIsCiAgICAgICAgICAgICAgcHJlZGljdGVkLmFubm90YXRpb24ubDIuc2NvcmUgPSBzcmF0XzAyYSRmZXRhbF9mdWxsX3ByZWRpY3RlZC5hbm5vdGF0aW9uLmwyLnNjb3JlLAogICAgICAgICAgICAgIHByZWRpY3RlZC5vcmdhbiA9IHNyYXRfMDJhJGZldGFsX2Z1bGxfcHJlZGljdGVkLm9yZ2FuLAogICAgICAgICAgICAgIHByZWRpY3RlZC5vcmdhbi5zY29yZSA9IHNyYXRfMDJhJGZldGFsX2Z1bGxfcHJlZGljdGVkLm9yZ2FuLnNjb3JlCiAgICAgICAgICAgICkKICAgICAgICAgICkKICAgICAgfQogICAgKQogIHdyaXRlX3JkcyhmZXRhbF9mdWxsLCBmdWxsX3Jlc3VsdHNfZmlsZSkKfSBlbHNlIHsKICBmZXRhbF9mdWxsIDwtIHJlYWRfcmRzKGZ1bGxfcmVzdWx0c19maWxlKQp9CmBgYAoKCiMjIyBMYWJlbCB0cmFuc2ZlciBmb3IgZmV0YWwga2lkbmV5CgoKYGBge3J9CmlmICghZmlsZS5leGlzdHMoa2lkbmV5X3Jlc3VsdHNfZmlsZSkpIHsKICAjIHJlYWQgcmVmZXJlbmNlCiAgcmVmIDwtIHJlYWRSRFMoZmlsZS5wYXRoKAogICAgbW9kdWxlX2Jhc2UsCiAgICAicmVzdWx0cyIsCiAgICAicmVmZXJlbmNlcyIsCiAgICAic3Rld2FydF9mb3JtYXR0ZWRfcmVmLnJkcyIKICApKQoKICAjIFB1bGwgb3V0IGluZm9ybWF0aW9uIGZyb20gdGhlIHJlZmVyZW5jZSBvYmplY3Qgd2UgbmVlZCBmb3IgbGFiZWwgdHJhbnNmZXIKICBraWRuZXlfcmVmZXJlbmNlIDwtIHJlZiRyZWZlcmVuY2UKICBraWRuZXlfcmVmZGF0YSA8LSByZWYkcmVmZGF0YQogIGtpZG5leV9kaW1zIDwtIHJlZiRkaW1zCiAga2lkbmV5X2Fubm90YXRpb25fY29sdW1ucyA8LSBjKAogICAgZ2x1ZTo6Z2x1ZSgicHJlZGljdGVkLntyZWYkYW5ub3RhdGlvbl9sZXZlbHN9IiksCiAgICBnbHVlOjpnbHVlKCJwcmVkaWN0ZWQue3JlZiRhbm5vdGF0aW9uX2xldmVsc30uc2NvcmUiKQogICkKCgogICMgUGVyZm9ybSBsYWJlbCB0cmFuc2ZlciB3aXRoIG5ldyBjb2RlCiAgYXNzYXkgPC0gIlJOQSIKICBmZXRhbF9raWRuZXkgPC0gc3JhdF9vYmplY3RzIHw+CiAgICBwdXJycjo6aW1hcCgKICAgICAgXChzcmF0LCBpZCkgewogICAgICAgIHNldC5zZWVkKHBhcmFtcyRzZWVkKQoKICAgICAgICBxdWVyeSA8LSBwcmVwYXJlX3F1ZXJ5KAogICAgICAgICAgc3JhdCwgCiAgICAgICAgICByb3duYW1lcyhraWRuZXlfcmVmZXJlbmNlKSwgCiAgICAgICAgICBhc3NheSwgCiAgICAgICAgICBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJzY3JhdGNoIiwgImhvbW9sb2dzLnJkcyIpCiAgICAgICAgKQogICAgICAgIHF1ZXJ5IDwtIHRyYW5zZmVyX2xhYmVscygKICAgICAgICAgIHF1ZXJ5LAogICAgICAgICAga2lkbmV5X3JlZmVyZW5jZSwKICAgICAgICAgIGtpZG5leV9kaW1zLAogICAgICAgICAga2lkbmV5X3JlZmRhdGEsCiAgICAgICAgICBxdWVyeS5hc3NheSA9IGFzc2F5CiAgICAgICAgKQoKICAgICAgICAjIFJlYWQgaW4gcmVzdWx0cyBmcm9tIGV4aXN0aW5nIEF6aW11dGggbGFiZWwgdHJhbnNmZXIgY29kZQogICAgICAgIHNyYXRfMDJiIDwtIHJlYWRSRFMoCiAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0X2RpciwgaWQsIGdsdWU6OmdsdWUoIjAyYi1mZXRhbF9raWRuZXlfbGFiZWwtdHJhbnNmZXJfe2lkfS5SZHMiKSkKICAgICAgICApCgogICAgICAgICMgY3JlYXRlIGZpbmFsIGRhdGEgZnJhbWUgd2l0aCBhbGwgYW5ub3RhdGlvbnMKICAgICAgICBxdWVyeUBtZXRhLmRhdGFbLCBraWRuZXlfYW5ub3RhdGlvbl9jb2x1bW5zXSB8PgogICAgICAgICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gImJhcmNvZGUiKSB8PgogICAgICAgICAgbXV0YXRlKAogICAgICAgICAgICBzYW1wbGVfaWQgPSBpZCwKICAgICAgICAgICAgdmVyc2lvbiA9ICJhZGFwdGVkX2F6aW11dGgiCiAgICAgICAgICApIHw+CiAgICAgICAgICAjIGV4aXN0aW5nIHJlc3VsdHMKICAgICAgICAgIGJpbmRfcm93cygKICAgICAgICAgICAgZGF0YS5mcmFtZSgKICAgICAgICAgICAgICBzYW1wbGVfaWQgPSBpZCwKICAgICAgICAgICAgICBiYXJjb2RlID0gY29sbmFtZXMoc3JhdF8wMmIpLAogICAgICAgICAgICAgIHZlcnNpb24gPSAiYXppbXV0aCIsCiAgICAgICAgICAgICAgcHJlZGljdGVkLmNvbXBhcnRtZW50ID0gc3JhdF8wMmIkZmV0YWxfa2lkbmV5X3ByZWRpY3RlZC5jb21wYXJ0bWVudCwKICAgICAgICAgICAgICBwcmVkaWN0ZWQuY29tcGFydG1lbnQuc2NvcmUgPSBzcmF0XzAyYiRmZXRhbF9raWRuZXlfcHJlZGljdGVkLmNvbXBhcnRtZW50LnNjb3JlLAogICAgICAgICAgICAgIHByZWRpY3RlZC5jZWxsX3R5cGUgPSBzcmF0XzAyYiRmZXRhbF9raWRuZXlfcHJlZGljdGVkLmNlbGxfdHlwZSwKICAgICAgICAgICAgICBwcmVkaWN0ZWQuY2VsbF90eXBlLnNjb3JlID0gc3JhdF8wMmIkZmV0YWxfa2lkbmV5X3ByZWRpY3RlZC5jZWxsX3R5cGUuc2NvcmUKICAgICAgICAgICAgKQogICAgICAgICAgKQogICAgICB9CiAgICApCgogIHdyaXRlX3JkcyhmZXRhbF9raWRuZXksIGtpZG5leV9yZXN1bHRzX2ZpbGUpCn0gZWxzZSB7CiAgZmV0YWxfa2lkbmV5IDwtIHJlYWRfcmRzKGtpZG5leV9yZXN1bHRzX2ZpbGUpCn0KYGBgCgoKIyMgQ29tcGFyZSByZXN1bHRzCgpXZSBleHBlY3Q6Ci0gVGhlIG1ham9yaXR5IG9mIGFubm90YXRpb25zIG1hdGNoIGJldHdlZW4gYXBwcm9hY2hlcywgd2l0aCBoZWF0bWFwIGNvdW50cyBwcmltYXJpbHkgZmFsbGluZyBhbG9uZyB0aGUgZGlhZ29uYWwKLSBBbnkgYW5ub3RhdGlvbnMgdGhhdCBkaXNhZ3JlZSBzaG91bGQgaGF2ZSBsb3cgc2NvcmVzCgoKIyMjIEZldGFsIGZ1bGwgcmVmZXJlbmNlCgpOb3RlIHRoYXQgcmVzdWx0cyBmcm9tIHRoZSBMMiByZWZlcmVuY2UgYXJlIG5vdCBwbG90dGVkIGJlY2F1c2UgdGhleSBhcmUgbm90IHVzZWQgaW4gY2VsbCB0eXBlIGFubm90YXRpb24uCgoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTR9CmZldGFsX2Z1bGwgfD4KICBwdXJycjo6d2FsaygKICAgIFwoZGF0KSB7CiAgICAgIGNvbXBhcmUoZGF0LCBwcmVkaWN0ZWQuYW5ub3RhdGlvbi5sMSwgcHJlZGljdGVkLmFubm90YXRpb24ubDEuc2NvcmUsICJsMSIpCiAgICAgIGNvbXBhcmUoZGF0LCBwcmVkaWN0ZWQub3JnYW4sIHByZWRpY3RlZC5vcmdhbi5zY29yZSwgIm9yZ2FuIikKICAgIH0KICApCmBgYAoKCiMjIyBGZXRhbCBraWRuZXkgcmVmZXJlbmNlCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xNH0KZmV0YWxfa2lkbmV5IHw+CiAgcHVycnI6OndhbGsoCiAgICBcKGRhdCkgewogICAgICBjb21wYXJlKGRhdCwgcHJlZGljdGVkLmNvbXBhcnRtZW50LCBwcmVkaWN0ZWQuY29tcGFydG1lbnQuc2NvcmUsICJjb21wYXJ0bWVudCIpCiAgICAgIGNvbXBhcmUoZGF0LCBwcmVkaWN0ZWQuY2VsbF90eXBlLCBwcmVkaWN0ZWQuY2VsbF90eXBlLnNjb3JlLCAiY2VsbF90eXBlIikKICAgIH0KICApCmBgYAoKCgojIyBDb25jbHVzaW9ucwoKVGhlIHZhc3QgbWFqb3JpdHkgb2YgdGhlIHRpbWUsIGxhYmVscyBhZ3JlZS4gCkdlbmVyYWxseSBzcGVha2luZywgd2hlbiBsYWJlbHMgZG8gbm90IGFncmVlLCB0aGVpciBhbm5vdGF0aW9uIHNjb3JlcyBhcmUgbXVjaCBsb3dlciwgd2hpY2ggaXMgYXMgZXhwZWN0ZWQuCiAKQWRkaXRpb25hbCBub3RhYmxlIGRpZmZlcmVuY2VzIGFyZSBzaG93biBpbiB0YWJsZXMgYmVsb3c6CiAgICAKIyMjIEZldGFsIGZ1bGwgcmVmZXJlbmNlOgoKLSBUaGUgQXppbXV0aC1hZGFwdGVkIGFwcHJvYWNoIG9jY2FzaW9uYWxseSBjYWxscyBraWRuZXkgb3Iga2lkbmV5LXJlbGF0ZWQgY2VsbHMgYXMgaW50ZXN0aW5lIG9yIGludGVzdGluZSBlcGl0aGVsaWFsCi0gU29tZSBvdGhlciBraWRuZXktcmVsYXRlZCBkaWZmZXJlbmNlcyBhcmUgbm90ZWQ6Cgp8IFNhbXBsZSB8IFJlZmVyZW5jZSB8IENvdW50IHwgQXppbXV0aCB8IEF6aW11dGgtYWRhcHRlZCB8CnwtLS0tLS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tfC0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLXwKfCBTQ1BTQzAwMDE3OSB8IEwxIHwgNzAgfCBNZXRhbmVwaHJpdGljIGNlbGxzIHwgSW50ZXN0aW5hbCBlcGl0aGVsaWFsIGNlbGxzIHwgCnwgU0NQU0MwMDAxNzkgfCBPcmdhbiB8IDY0IHwgS2lkbmV5IHwgSW50ZXN0aW5lIHwgCnwgU0NQU0MwMDAxNzkgfCBPcmdhbiB8IDIwIHwgTHVuZyB8IEtpZG5leSB8IAp8IFNDUFNDMDAwMTk0IHwgTDEgfCA2MCB8IFN0cm9tYWwgY2VsbHMgfCBNZXNhbmdpYWwgY2VsbHMgfCAKfCBTQ1BTQzAwMDE5NCB8IE9yZ2FuIHwgMzUgfCBLaWRuZXkgfCBJbnRlc3RpbmUgfCAKfCBTQ1BTQzAwMDE5NCB8IE9yZ2FuIHwgMzYgfCBMdW5nIHwgS2lkbmV5IHwgCnwgU0NQU0MwMDAyMDUgfCBPcmdhbiB8IDU2IHwgS2lkbmV5IHwgSW50ZXN0aW5lIHwKfCBTQ1BTQzAwMDIwOCB8IEwxIHwgMTAxIHwgTWVzYW5naWFsIGNlbGxzIHwgTWV0YW5lcGhyaXRpYyBjZWxscyB8ICAKfCBTQ1BTQzAwMDIwOCB8IEwxIHwgNzUgfCBJbnRlc3RpbmFsIGVwaXRoZWxpYWwgY2VsbHMgfCBNZXRhbmVwaHJpdGljIGNlbGxzIHwgIAp8IFNDUFNDMDAwMjA4IHwgT3JnYW4gfCAxNDkgfCBLaWRuZXkgfCBJbnRlc3RpbmUgfCAKCgojIyMgRmV0YWwga2lkbmV5IHJlZmVyZW5jZToKCi0gTW9zdCBvZiB0aGUgY2VsbCB0eXBlIGRpZmZlcmVuY2VzIGFyZSBub3QgaW4gdGhlIHRhYmxlIGJlbG93IGJlY2F1c2UgdGhleSBhcmUgbm90IG5lY2Vzc2FyaWx5IGJpb2xvZ2ljYWxseSBtZWFuaW5nZnVsIGZvciBvdXIgcHVycG9zZXM6CiAgIC0gYGtpZG5leSBjZWxsYCB2cyBgcG9kb2N5dGVgCiAgIC0gYGtpZG5leSBlcGl0aGVsaWFsIGNlbGxgIHZzIGBraWRuZXkgY2VsbGAgCiAgIC0gYG1lc2VuY2h5bWFsIGNlbGxgIHZzIGBtZXNlbmNoeW1hbCBzdGVtIGNlbGxgIAoKCnwgU2FtcGxlIHwgUmVmZXJlbmNlIHwgQ291bnQgfCBBemltdXRoIHwgQXppbXV0aC1hZGFwdGVkIHwKfC0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tfAp8IFNDUFNDMDAwMTc5IHwgY2VsbCB0eXBlIHwgOTQgfCBtZXNlbmNoeW1hbCBjZWxsIHwga2lkbmV5IGVwaXRoZWxpYWwgY2VsbCB8IAp8IFNDUFNDMDAwMjA1IHwgY29tcGFydG1lbnQgIHwgNTIgfCBmZXRhbCBuZXBocm9uIHwgIHN0cm9tYSB8IAoKCiMjIFNlc3Npb24gSW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCg==